package cas.ihep.hep.util;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;

public class Buffer implements DataInput,DataOutput{

    private byte[] buf;
    private int offset,length;

    public Buffer(){
        buf=null;
        offset=length=0;
    }

    public Buffer(int sz){
        if (sz < 0) {
            throw new IllegalArgumentException("Negative initial size: "
                    + sz);
        }
        buf=new byte[sz];
        offset=0;
        length=sz;
    }

    public byte[] byteArray(){
        byte[] ret=buf;
        buf=null;
        offset=length=0;
        return ret;
    }

    public int pread(int off){
        return off<length?buf[off]&0xff:-1;
    }

    public int read() {
        return (offset < length) ? (buf[offset++] & 0xff) : -1;
    }

    public int read(byte[] b,int off,int len){
        if (b == null) {
            throw new NullPointerException();
        } else if (off < 0 || len < 0 || len > b.length - off) {
            throw new IndexOutOfBoundsException();
        }

        if (offset >= length) {
            return -1;
        }

        int avail = length - offset;
        if (len > avail) {
            len = avail;
        }
        if (len <= 0) {
            return 0;
        }
        System.arraycopy(buf, offset, b, off, len);
        offset += len;
        return len;
    }

    public void write(int b){
        if(offset>=length){
            grow(offset+1);
        }
        buf[offset++]=(byte)b;
    }

    public void write(ByteBuffer buffer){
        int len=buffer.remaining();
        if(offset+len>length){
            grow(offset+len);
        }
        buffer.get(buf,offset,len);
        offset+=len;
    }

    public int available(){
        return length-offset;
    }

    @Override
    public void write(byte[] b) {
        write(b,0,b.length);
    }

    public void write(byte b[], int off, int len) {
        if(b==null){
            throw new NullPointerException();
        }
        if ((off < 0) || (off > b.length) || (len < 0) ||
                ((off + len) - b.length > 0)) {
            throw new IndexOutOfBoundsException();
        }
        if(offset+len>=length){
            grow(offset+len);
        }
        System.arraycopy(b, off, buf, offset, len);
        offset += len;
    }

    private static void int2ByteArray(int v,byte[] buf,int offset){
        buf[offset]= (byte) ((v>>>24)&0xff);
        buf[offset+1]= (byte) ((v>>>16)&0xff);
        buf[offset+2]= (byte) ((v>>>8)&0xff);
        buf[offset+3]= (byte) (v&0xff);
    }

    private static void short2ByteArray(short sh,byte[] buf,int offset){
        buf[offset]= (byte) ((sh>>>8)&0xff);
        buf[offset+1]= (byte) (sh&0xff);
    }

    public static void long2ByteArray(long v,byte[] buf,int offset){
        buf[offset]= (byte) ((v>>>56)&0xff);
        buf[offset+1]= (byte) ((v>>>48)&0xff);
        buf[offset+2]= (byte) ((v>>>40)&0xff);
        buf[offset+3]= (byte) ((v>>>32)&0xff);
        buf[offset+4]= (byte) ((v>>>24)&0xff);
        buf[offset+5]= (byte) ((v>>>16)&0xff);
        buf[offset+6]= (byte) ((v>>>8)&0xff);
        buf[offset+7]= (byte) (v&0xff);
    }

    @Override
    public void writeBoolean(boolean v) {
        write(v?1:0);
    }

    @Override
    public void writeByte(int v) {
        write(v);
    }

    @Override
    public void writeShort(int v) {
        if(offset+2>length){
            grow(offset+2);
        }
        short2ByteArray((short)v,buf,offset);
        offset+=2;
    }

    @Override
    public void writeChar(int v) {
        writeShort(v);
    }

    @Override
    public void writeInt(int v) {
        if(offset+4>length){
            grow(offset+4);
        }
        int2ByteArray(v,buf,offset);
        offset+=4;
    }

    @Override
    public void writeLong(long v) {
        if(offset+8>length){
            grow(offset+8);
        }
        long2ByteArray(v,buf,offset);
        offset+=8;
    }

    @Override
    public void writeFloat(float v) {
        writeInt(Float.floatToIntBits(v));
    }

    @Override
    public void writeDouble(double v) {
        writeLong(Double.doubleToLongBits(v));
    }

    @Override
    public void writeBytes(String s) {
        throw new RuntimeException("Unsupported operation");
    }

    @Override
    public void writeChars(String s) {
        throw new RuntimeException("Unsupported operation");
    }

    @Override
    public void writeUTF(String s) {
        throw new RuntimeException("Unsupported operation");
    }

    public void reset(int pos){
        if(pos<0 || pos>length){
            throw new IndexOutOfBoundsException();
        }
        offset=0;
    }

    public void limit(int lim){
        if(lim<offset || lim>length){
            throw new IndexOutOfBoundsException();
        }
        length=lim;
    }

    public int position(){
        return offset;
    }

    public int size(){
        return length;
    }

    /**
     * The maximum size of array to allocate.
     * Some VMs reserve some header words in an array.
     * Attempts to allocate larger arrays may result in
     * OutOfMemoryError: Requested array size exceeds VM limit
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     */
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = length;
        int newCapacity = oldCapacity << 1;
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        buf = Arrays.copyOf(buf, newCapacity);
        length=newCapacity;
    }

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
                Integer.MAX_VALUE :
                MAX_ARRAY_SIZE;
    }

    @Override
    public void readFully(byte[] b) throws IOException {
        readFully(b,0,b.length);
    }

    @Override
    public void readFully(byte[] b, int off, int len) throws IOException {
        if (len < 0)
            throw new IndexOutOfBoundsException();
        int n = 0;
        while (n < len) {
            int count = read(b, off + n, len - n);
            if (count < 0)
                throw new EOFException();
            n += count;
        }
    }

    @Override
    public int skipBytes(int n) {
        if(n<0){
            n=Math.min(offset,-n);
            offset-=n;
        }else {
            n = Math.min(n, length - offset);
            offset += n;
        }
        return n;
    }

    @Override
    public boolean readBoolean() throws IOException {
        int r=read();
        if(r!=-1){
            return r!=0;
        }
        throw new EOFException();
    }

    @Override
    public byte readByte() throws IOException {
        int r=read();
        if(r!=-1){
            return (byte)r;
        }
        throw new EOFException();
    }

    @Override
    public int readUnsignedByte() throws IOException {
        return readByte()&0xff;
    }

    public static short byteArray2Short(byte[] arr, int off){
        return (short) (Byte.toUnsignedInt(arr[off])<<8 | Byte.toUnsignedInt(arr[off+1]));
    }

    public static int byteArray2Int(byte[] arr,int off){
        return Byte.toUnsignedInt(arr[off])<<24 | Byte.toUnsignedInt(arr[off+1])<<16 | Byte.toUnsignedInt(arr[off+2])<<8 | Byte.toUnsignedInt(arr[off+3]);
    }

    public static long byteArray2Long(byte[] arr, int off){
        return Byte.toUnsignedLong(arr[off])<<56 | Byte.toUnsignedLong(arr[off+1])<<48 | Byte.toUnsignedLong(arr[off+2])<<40 | Byte.toUnsignedLong(arr[off+3])<<32 |
                Byte.toUnsignedInt(arr[off+4])<<24 | Byte.toUnsignedInt(arr[off+5])<<16 | Byte.toUnsignedInt(arr[off+6])<<8 | Byte.toUnsignedInt(arr[off+7]);
    }

    @Override
    public short readShort() throws IOException {
        if(offset+2<=length){
            short ret=byteArray2Short(buf,offset);
            offset+=2;
            return ret;
        }
        throw new EOFException();
    }

    @Override
    public int readUnsignedShort() throws IOException {
        return readShort()&0xffff;
    }

    @Override
    public char readChar() throws IOException {
        return (char)readUnsignedShort();
    }

    @Override
    public int readInt() throws IOException {
        if(offset+4<=length){
            int ret=byteArray2Int(buf,offset);
            offset+=4;
            return ret;
        }
        throw new EOFException();
    }

    @Override
    public long readLong() throws IOException {
        if (offset+8<=length){
            long ret=byteArray2Long(buf,offset);
            offset+=8;
            return ret;
        }
        throw new EOFException();
    }

    @Override
    public float readFloat() throws IOException {
        return Float.intBitsToFloat(readInt());
    }

    @Override
    public double readDouble() throws IOException {
        return Double.longBitsToDouble(readLong());
    }

    @Override
    public String readLine() {
        throw new RuntimeException("Unsupported operation");
    }

    @Override
    public String readUTF() {
        throw new RuntimeException("Unsupported operation");
    }
}
